//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using ManagementStudioServices.Interfaces;
using Microsoft.Research.ScientificWorkflow.ManagementStudioModels;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
using Microsoft.Practices.Unity;
using SR = Microsoft.Research.DataLayer;
using System.Collections.Generic;
using Microsoft.Research.ScientificWorkflow.UIDesigner;
using Microsoft.Research.ScientificWorkflow.TridentComposer;
using Microsoft.Research.DataLayer;
using System.Globalization;


namespace Microsoft.Research.ScientificWorkflow.ManagementStudioControls.WorkflowManagerModule
{
    /// <summary>
    /// The Presentation logic for the execute now window.
    /// </summary>
    class ExecuteWorkflowViewPresenter : DependencyObject, INotifyPropertyChanged
    {
        /// <summary>
        /// The unity container instance.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// The workflow manager service instance.
        /// </summary>
        private IWorkflowManagerService workflowManagerService;

        /// <summary>
        /// The execute workflow usercontrol.
        /// </summary>
        private ExecuteWorkflowView view;

        /// <summary>
        /// The ID of the workflow selected.
        /// </summary>
        private Guid workflowId;

        /// <summary>
        /// Notes corresponding to the job.
        /// </summary>
        private string jobNotes;

        /// <summary>
        /// The window instance.
        /// </summary>
        private Window executeWorkflowWindow;

        /// <summary>
        /// The selected node.
        /// </summary>
        private SR.Machine selectedNode;

        /// <summary>
        /// The selected user.
        /// </summary>
        private SR.User selectedUser;

        /// <summary>
        /// The workflow required parameters.
        /// </summary>
        private Collection<ParameterDescriptionModel> workflowRequiredParameters;


        public DelegateCommand<bool> RefreshNodesCommand;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="container"></param>
        /// <param name="view"></param>
        /// <param name="service"></param>
        public ExecuteWorkflowViewPresenter(Guid workflowId, IUnityContainer container, ExecuteWorkflowView view, IWorkflowManagerService service)
        {
            this.workflowId = workflowId;
            this.container = container;
            this.view = view;
            this.workflowManagerService = service;

            view.DataContext = this;
            this.AdditionalParameters = new ObservableCollection<AdditionalParametersModel>();
            this.NodeList = new ObservableCollection<Machine>();
            // Initialize the commands.
            this.CloseWindowCommand = new DelegateCommand<object>(p => ExecuteCloseWindow());
            this.CreateJobCommand = new DelegateCommand<object>(p => ExecuteJobCreateCommand());
            this.AddAdditionalParameterCommand = new DelegateCommand<object>(p => AddAdditionalParametersExecute());
            this.DeleteAdditionalParameterCommand = 
                new DelegateCommand<AdditionalParametersModel>(this.DeleteAdditionalParametersExecute);

            this.RefreshNodesCommand = new DelegateCommand<bool>(ExecuteRefreshNodesCommand);

        }

        /// <summary>
        /// The workflow name.
        /// </summary>
        public string WorkflowName { get; private set; }

        /// <summary>
        /// The job name.
        /// </summary>
        public string JobName { get; set; }

        /// <summary>
        /// The list of nodes in the registry.
        /// </summary>
        public ObservableCollection<SR.Machine> NodeList { get; private set; }

        /// <summary>
        /// The list of users.
        /// </summary>
        public Collection<SR.User> UserList { get; private set; }

        /// <summary>
        /// Gets the additional parameters collection.
        /// </summary>
        public ObservableCollection<AdditionalParametersModel> AdditionalParameters { get; private set; }

        /// <summary>
        /// The command which creates a Job in the registry.
        /// </summary>
        public ICommand CreateJobCommand { get; private set; }

        /// <summary>
        /// Command to close the execute window.
        /// </summary>
        public ICommand CloseWindowCommand { get; private set; }

        /// <summary>
        /// Command to add a additional parameter.
        /// </summary>
        public ICommand AddAdditionalParameterCommand { get; private set; }

        /// <summary>
        /// Command to remove an additional parameter.
        /// </summary>
        public DelegateCommand<AdditionalParametersModel> DeleteAdditionalParameterCommand { get; private set; }

        /// <summary>
        /// The model of the workflow to be displayed in the window.
        /// </summary>
        public TridentWorkflowModel workflowModel { get; private set; }

        /// <summary>
        /// The selected machine in the combobox.
        /// </summary>
        public SR.Machine SelectedMachine
        {
            get
            {
                return this.selectedNode;
            }
            set
            {
                this.selectedNode = value;
                this.OnNotifyPropertyChanged("SelectedMachine");
            }
        }

        /// <summary>
        /// The selected machine in the combobox.
        /// </summary>
        public SR.User SelectedUser
        {
            get
            {
                return this.selectedUser;
            }
            set
            {
                this.selectedUser = value;
                this.OnNotifyPropertyChanged("SelectedUser");
            }
        }

        /// <summary>
        /// Gets or sets the notes corresponding to the job.
        /// </summary>
        public string JobNotes
        {
            get
            {
                return this.jobNotes;
            }
            set
            {
                this.jobNotes = value;
            }
        }

        /// <summary>
        /// Flag to show if the monitor window is to be displayed once the job starts.
        /// </summary>
        public bool ShowMonitor { get; set; }

        /// <summary>
        /// Flag to check if all the properties have valid values in the property pane
        /// </summary>
        public static readonly DependencyProperty PropertiesValidProperty =
        DependencyProperty.Register("PropertiesValid", typeof(Boolean), typeof(ExecuteWorkflowViewPresenter), new PropertyMetadata(true));

        /// <summary>
        /// Flag to check if all the properties have valid values in the property pane.
        /// </summary>
        public bool PropertiesValid
        {
            get { return (bool)GetValue(PropertiesValidProperty); }
            set { SetValue(PropertiesValidProperty, value); }
        }

        /// <summary>
        /// Display the execute workflow window.
        /// </summary>
        public void DisplayExecuteWorkflowWindow()
        {
            // Get the model and the node list.
            this.CreateWorkflowModel();

            this.WorkflowName = this.workflowModel.GetDisplayName;

            this.ExecuteRefreshNodesCommand(true);
            this.GetUserList();

            // Set the current user as the default scheduler.
            this.SelectDefaultUser();

            // Create a window and add the view into it.
            this.executeWorkflowWindow = new Window();
            this.view.HeaderDrag += (sender, e) => { this.executeWorkflowWindow.DragMove(); };
            this.executeWorkflowWindow.Owner = Application.Current.MainWindow;
            this.executeWorkflowWindow.ShowInTaskbar = false;
            this.executeWorkflowWindow.WindowStyle = WindowStyle.None;
            this.executeWorkflowWindow.BorderThickness = new Thickness(0);
            this.executeWorkflowWindow.BorderBrush = Brushes.Transparent;
            this.executeWorkflowWindow.Content = this.view;
            this.executeWorkflowWindow.SizeToContent = SizeToContent.WidthAndHeight;
            this.executeWorkflowWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            this.executeWorkflowWindow.ResizeMode = ResizeMode.NoResize;
            this.view.Initialize(this.workflowModel,this.workflowRequiredParameters, this.container.Resolve<Dictionary<string, DataTypeModel>>("Datatypes"));
            this.executeWorkflowWindow.ShowDialog();
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// The property changed event. Fired when a proprty value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property which has been modified</param>
        private void OnNotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion 

        /// <summary>
        /// Create the workflow model based on the workflow ID.
        /// </summary>
        private void CreateWorkflowModel()
        {
            SR.Activity workflow = this.workflowManagerService.GetWorkflow(this.workflowId);

            ModelFactory modelFactory = new ModelFactory(null, null);

            this.workflowModel = new TridentWorkflowModel(workflow, modelFactory);

            this.workflowRequiredParameters = this.workflowModel.GetWorkflowRequiredParameters();
        }

        /// <summary>
        /// Retrieve the list of nodes present.
        /// </summary>
        private void ExecuteRefreshNodesCommand(bool selectDefaultNode)
        {
            Machine defaultNode = null;
            bool lastSelectedNodeExist = false;
            Machine lastSelectedMachine = this.SelectedMachine;

            this.NodeList.Clear();
            Collection<Machine> machines = this.workflowManagerService.GetAllMachines(this.workflowModel.IsInteractive);
            
            if (machines != null && machines.Count > 0)
            {
                foreach (Machine mac in machines)
                {
                    this.NodeList.Add(mac);
                    if (mac.Name.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase))
                    {
                        //this.SelectedMachine = mac;
                        defaultNode = mac;
                    }
                    // If last selected machine exist in the new node collection
                    if ((lastSelectedMachine != null) && (mac.Name.Equals(lastSelectedMachine.Name, StringComparison.OrdinalIgnoreCase)))
                    {
                        lastSelectedNodeExist = true;
                    }
                }
                // If default value to be set, check if the Executing machine name is available in the machines list
                // if available select it, else select the first node
                if (selectDefaultNode)
                {
                    this.SelectedMachine = defaultNode;
                }
                else
                {
                    //Set the previously selected value
                    if (lastSelectedNodeExist)
                    {
                        this.SelectedMachine = lastSelectedMachine;
                    }
                    else
                    {
                        this.SelectedMachine = null;
                    }
                }
            }
        }

        /// <summary>
        /// Retrieve the list of Users present.
        /// </summary>
        private void GetUserList()
        {
            this.UserList = this.workflowManagerService.GetAllAuthenticatedUsers();
        }

        /// <summary>
        /// Select the current user as the default user.
        /// </summary>
        private void SelectDefaultUser()
        {
            string userName = TridentAuthentication.LoggedUserInUserName; //Environment.UserName;

            if (this.UserList != null && this.UserList.Count > 0)
            {
                this.SelectedUser = this.UserList.FirstOrDefault(user => user.Name.Equals(userName, StringComparison.OrdinalIgnoreCase));

                if (this.SelectedUser == null)
                {
                    // If the current user is missing in the user list then select the first in the list.
                    this.SelectedUser = this.UserList[0];
                }
            }
        }

        /// <summary>
        /// Create a job in the registry.
        /// </summary>
        private void ExecuteJobCreateCommand()
        {
            try
            {
                // Check if all values are entered in the property pane.
                if (!PropertiesValid)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("ReqPropertiesNotEnteredError"));
                    return;
                }

                if (!CheckIfAdditionalParametersValid())
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("AdditionalParameterError"));
                    return;
                }

                if (!IsSelectedMachineValid(this.selectedNode))
                {
                    return;
                }
                JobDetailModel newJob = new JobDetailModel();
                newJob.WorkflowId = this.workflowId;
                newJob.JobName = this.JobName;
                newJob.Notes = this.JobNotes;

                // Add the additional parameters to the new job details.
                foreach (AdditionalParametersModel additionalParameter in this.AdditionalParameters)
                {
                    newJob.AdditionalParameters.Add(additionalParameter);
                }

                // Create the list of parameters and their values for the workflow.
                newJob.ParameterInfo = this.workflowModel.CreateWorkflowParameterInfo();

                // Create a job in registry.
                JobComposer wfClient = new JobComposer(this.workflowManagerService.CurrentConnection);
                SR.Job newRegJob = wfClient.ExecuteWFOnMachine(newJob, this.selectedNode, this.selectedUser, false);
                
                if (this.ShowMonitor)
                {
                    // Open the monitor window to track the progress of the workflow.
                    this.workflowManagerService.OpenMonitor(((SR.IObject)newRegJob).ID);
                }
                // Close the window.
                this.executeWorkflowWindow.Close();
            }
            catch (TridentCustomException ex)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);

                // Close the window.
                this.executeWorkflowWindow.Close();
            }
        }

        /// <summary>
        /// Determines whether the selected machine is valid for the job to be executed
        /// </summary>
        /// <param name="selectedMachine"></param>
        /// <returns></returns>
        private bool IsSelectedMachineValid(Machine selectedMachine)
        {
            bool isValid = false;
            if (selectedMachine != null)
            {
                Machine machine = Machine.Load((selectedMachine as IObject).ID, this.workflowManagerService.CurrentConnection);
                if (!machine.IsDeleted && machine.Availability == ExecutorAvailabiltiy.Online)
                {
                    isValid = true;
                }
                else
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("InvalidNodeSelectedToExecuteJob"));
                }
            }
            else
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("MachineCannotBeEmpty"));
            }
            return isValid;
        }

        /// <summary>
        /// Close the window.
        /// </summary>
        private void ExecuteCloseWindow()
        {
            this.view.DataContext = null;
            this.executeWorkflowWindow.Close();
        }

        /// <summary>
        /// Add an additional parameter to the list.
        /// </summary>
        private void AddAdditionalParametersExecute()
        {
            this.AdditionalParameters.Add(new AdditionalParametersModel());
        }

        /// <summary>
        /// Remove an additional parameter in the list.
        /// </summary>
        /// <param name="model">The additional parameter to delete.</param>
        private void DeleteAdditionalParametersExecute(AdditionalParametersModel model)
        {
            this.AdditionalParameters.Remove(model);
        }

        /// <summary>
        /// Check if the additional parameters have valid values/ names.
        /// </summary>
        /// <returns>True if the additional parameters are valid. False otherwise.</returns>
        private bool CheckIfAdditionalParametersValid()
        {
            AdditionalParametersModel invalidModel = this.AdditionalParameters.FirstOrDefault(p => (string.IsNullOrEmpty(p.Parameter) || string.IsNullOrEmpty(p.ParameterValue)));
            return (invalidModel == null);
        }
    }
}
